from Tkinter import LAST
from math import sqrt

from nipponsteel import *
from nipponsteel.gui.player.geometry import *


class Sprite(object):
    def __init__(self, player):
        self.player = player
        self.geometry = []
        self.items = []
        self.layer = 0
        self.x = 0.0
        self.y = 0.0
        self.visible = False
        self.moving = False
        self.id_tag = "%s@%xd" % (self.__class__.__name__, id(self))
        self.layer_tag = "layer%d" % self.layer
        
    @property
    def coords(self):
        return (self.x, self.y)
        
    def clear(self):
        if self.visible:
            self.player.canvas.delete(self.id_tag)
            self.items = []
            self.visible = False
            
    def draw(self):
        for geom in self.geometry:
            item = geom.draw(self.player.canvas, x_offset=self.x, y_offset=self.y, 
                             scale=self.player.scale * self.player.pixel_ratio,
                             tags=(self.id_tag, self.layer_tag))
            self.items.append(item)
        self.player.layers.set(self.id_tag, self.layer)
        self.visible = True
        
    def redraw(self):
        self.clear()
        self.draw()
        
    def set_layer(self, layer):
        self.layer = layer
        if self.visible:
            # swap layer tag on the sprite
            self.player.canvas.dtag(self.id_tag, self.layer_tag)
            self.layer_tag = "layer%d" % layer
            self.player.canvas.addtag_withtag(self.layer_tag, self.id_tag)
            # reposition sprite in the canvas display list
            self.player.layers.set(self.id_tag, self.layer)
        else:
            self.layer_tag = "layer%d" % layer
            
    def set_pos(self, x=None, y=None):
        if x is None:
            x = self.x
        if y is None:
            y = self.y
        if self.visible:
            dx = x - self.x
            dy = y - self.y
            self.player.canvas.move(self.id_tag, self.player.scaled(dx), self.player.scaled(dy))
        self.x = x
        self.y = y
        
    def move_to(self, x, y, duration=1.0, at_end=None):
        self.moving = True
        frame_time = self.player.frame_time
        duration_ms = duration * 1000.0
        if self.visible and duration_ms > frame_time:
            dx = (x - self.x) * frame_time / duration_ms
            dy = (y - self.y) * frame_time / duration_ms
            self.set_pos(self.x + dx, self.y + dy)
            remaining_time = duration - frame_time / 1000.0
            self.player.after(frame_time, self.move_to, x, y, remaining_time, at_end)
        else:
            self.set_pos(x, y)
            if at_end is not None:
                self.player.after(0, at_end)
            self.moving = False
            
    def move(self, dx=0.0, dy=0.0, duration=1.0, at_end=None):
        self.move_to(self.x + dx, self.y + dy, duration, at_end)
        
    def bind(self, sequence, callback, add=False):
        self.player.canvas.tag_bind(self.id_tag, sequence, callback, add=add)
        
        
class FloorSprite(Sprite):
    def __init__(self, player, rectangle):
        Sprite.__init__(self, player)
        self.set_pos(*rectangle.center)
        self.geometry.append(Rectangle(rectangle.width, rectangle.height, fill="white", width=0))
        
        
class EntranceSprite(Sprite):
    facing_up = True
    color = "light blue"
    
    def __init__(self, player, place):
        Sprite.__init__(self, player)
        self.place = place
        self.set_pos(*place.meta.coords)
        self.geometry.append(Triangle(1, 1, facing_up=self.facing_up, fill=self.color, width=0))
        
    def draw(self):
        self.geometry[0].width  = self.player.stack_size
        self.geometry[0].height = self.player.stack_size
        Sprite.draw(self)
        
        
class ExitSprite(EntranceSprite):
    facing_up = False
    color = "light pink"
    
    
class StackSprite(Sprite):
    def __init__(self, player, place):
        Sprite.__init__(self, player)
        self.place = place
        self.set_pos(*place.meta.coords)
        self.geometry.extend([Oval(1, 1, fill=self.color, width=0),
                              Oval(1, 1, fill=None, width=1)])
        
    @property
    def color(self):
        red = int(0.5 + 255 * self.occupation)
        if red == 0:
            return None
        green = 255 - red
        blue  = 0
        return "#%02x%02x%02x" % (red, green, blue)
        
    @property
    def occupation(self):
        return self.place.height / self.player.tape.warehouse.meta.max_height
        
    def draw(self):
        occupation = self.occupation
        diameter = self.player.stack_size * (0.1 + occupation * 0.9)
        self.geometry[0].width  = diameter
        self.geometry[0].height = diameter
        self.geometry[0].options["fill"] = self.color
        
        self.geometry[1].width  = self.player.stack_size
        self.geometry[1].height = self.player.stack_size
        Sprite.draw(self)
        
        
def PlaceSprite(player, place):
    if place.is_entrance:
        return EntranceSprite(player, place)
    if place.is_exit:
        return ExitSprite(player, place)
    return StackSprite(player, place)
    
    
class MoveSprite(Sprite):
    def __init__(self, player, move):
        Sprite.__init__(self, player)
        place = player.tape.warehouse.places[move.place]
        crane = player.tape.warehouse.handlers[move.handler]
        p0 = player.sprite[crane].coords
        p1 = place.meta.coords
        color = "red" if move.is_putdown else "green"
        self.geometry.append(Line(p0, p1, fill=color, arrow=LAST, width=len(move.items)))
        
        
class CraneSprite(Sprite):
    def __init__(self, player, crane):
        Sprite.__init__(self, player)
        self.crane = crane
        self.set_pos(*crane.meta.initial_coords)
        self.geometry.extend([Rectangle(1, 1, fill="black", width=0),
                              Rectangle(1, 1, fill=None, width=0)])
        
    @property
    def color(self):
        red = int(0.5 + 255 * self.occupation)
        if red == 0:
            return None
        green = 255 - red
        blue  = 0
        return "#%02x%02x%02x" % (red, green, blue)
        
    @property
    def occupation(self):
        return float(len(self.crane.items)) / self.crane.meta.capacity
        
    def draw(self):
        size = self.player.stack_size * self.occupation
        self.geometry[1].width  = size
        self.geometry[1].height = size
        self.geometry[1].options["fill"] = self.color
        
        self.geometry[0].width  = self.player.stack_size
        self.geometry[0].height = self.player.stack_size
        Sprite.draw(self)
        